package com.campus.customer.log;

import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.campus.customer.service.SessionService;
import com.campus.customer.vo.UserInfoVo;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Order(1)
@Aspect
@Component
public class LoggerAspect {

    private final Logger logger= LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SessionService sessionService;
    @Autowired
    private LogConfig logConfig;
    @Autowired
    private ObjectMapper objectMapper;


    /**
     * Pointcut that matches all repositories, services and Web REST endpoints.<br/>
     * 也可以使用@within(org.springframework.stereotype.Service)，拦截带有 @Service 注解的类的所有方法，
     * 等同于within(@org.springframework.stereotype.Service *).
     * 同理，@annotation(org.springframework.web.bind.annotation.RequestMapping)，可以拦截带有@RquestMapping的注解方法<br/>
     * Pointcut that matches all Spring beans in the application's main packages.
     * within(com.clouderwork..*)
     * execution(@(@org.springframework.web.bind.annotation.RequestMapping *) * *(..))会拦截所有的请求
     */
//    @Pointcut("within(@org.springframework.stereotype.Repository *)" +
//            " || within(@org.springframework.stereotype.Service *)" +
//            " || within(@org.springframework.stereotype.Controller *)")
    @Pointcut("execution(@(@org.springframework.web.bind.annotation.RequestMapping *) * *(..))")
    public void logPointcut() {
        // Method is empty as this is just a Pointcut, the implementations are in the advices.
    }

    /**
     * Advice that logs methods throwing exceptions.
     *
     * @param joinPoint join point for advice
     * @param e exception
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        logger.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(), e.getCause() != null ? e.getCause() : "NULL");
    }

    /**
     * Advice that logs when a method is entered and exited.
     *
     * @param joinPoint join point for advice
     * @return result
     * @throws Throwable throws IllegalArgumentException
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        LocalDateTime beginTime = LocalDateTime.now();

        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method signatureMethod = signature.getMethod();

        Object[] args = joinPoint.getArgs();
        List<Object> argList = new ArrayList<>();
        if(args != null && args.length > 0){
            for(Object arg : args){
                argList.add(arg);
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Enter: {}.{}() with argument[s] = {}", signature.getDeclaringTypeName(),
                    signature.getName(), JSON.toJSONString(argList));
        }

        LogInfoEntity logInfoEntity = new LogInfoEntity();
        UserInfoVo curreUser = null;
        try {
            curreUser = sessionService.getUserInfo();
        } catch (Exception e) {
        }
        if(curreUser != null){
            logInfoEntity.setOperatorName(curreUser.getNickname());
        }
        logInfoEntity.setBeginTime(beginTime);
        //获取上下文request容器
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            // 非HTTP请求上下文，直接执行业务逻辑
            return joinPoint.proceed();
        }
        HttpServletRequest request = requestAttributes.getRequest();
        String method = request.getMethod();


        Object result = null;
        try {
            result = joinPoint.proceed();
            if (logger.isDebugEnabled()) {
                logger.debug("Exit: {}.{}() with result = {}", signature.getDeclaringTypeName(),
                        signature.getName(), result);
            }
            logInfoEntity.setCode("0");
            logInfoEntity.setMessage(objectMapper.writeValueAsString(result));
            return result;
        } catch (Exception e) {
            logInfoEntity.setCode("1");
//            StringWriter stringWriter = new StringWriter();
//            PrintWriter printWriter = new PrintWriter(stringWriter);
//            e.printStackTrace(printWriter);
//            logInfoEntity.setMessage(stringWriter.toString());
            logInfoEntity.setMessage(e.toString());

            throw e;
        }finally {
            LocalDateTime endTime = LocalDateTime.now();

            if(logConfig.isEnabled()){
                boolean ignoreRequestType = false;
                List<String> requestTypeList = Arrays.asList(logConfig.getRequestTypesIgnore().split(","));
                for(String requestType : requestTypeList){
                    if(!StringUtils.isEmpty(requestType.trim())&& requestType.trim().equalsIgnoreCase(method)){
                        ignoreRequestType = true;
                        break;
                    }
                }

                boolean ignoreRequestUri = false;
                List<String> requestUriList = Arrays.asList(logConfig.getRequestUrisIgnore().split(","));
                PathMatcher pathMatcher = new AntPathMatcher();
                for(String requestUri : requestUriList){
                    if(!StringUtils.isEmpty(requestUri.trim()) && pathMatcher.match(requestUri.trim(),request.getRequestURI())){
                        ignoreRequestUri = true;
                        break;
                    }
                }

                for(int i=argList.size()-1;i>=0;i--){
                    if(argList.get(i) instanceof MultipartFile || argList.get(i) instanceof HttpServletRequest || argList.get(i) instanceof HttpServletResponse){
                        argList.remove(i);
                    }
                }

                String operationUrl = request.getRequestURL().toString();
                String argsStr = objectMapper.writeValueAsString(argList);

                if(ignoreRequestType){
                    logger.debug("{}请求类型不记录日志",method);
                }else if(ignoreRequestUri){
                    logger.debug("{}请求URL不记录日志",request.getRequestURL().toString());
                }else{
                    //操作类型
                    logInfoEntity.setOperationType(method);
                    //操作url
                    logInfoEntity.setOperationUrl(operationUrl);
                    logInfoEntity.setClassName(signature.getDeclaringTypeName());
                    logInfoEntity.setMethodName(signature.getName());
                    logInfoEntity.setArgs(argsStr);
                    logInfoEntity.setClientIp(ServletUtil.getClientIP(request, null));
                    logInfoEntity.setEndTime(endTime);

                    ApiOperation apiOperation = signatureMethod.getAnnotation(ApiOperation.class);
                    if(apiOperation != null){
                        logInfoEntity.setDescription(apiOperation.value());
                    }
                    logger.info("输出日志：{}", objectMapper.writeValueAsString(logInfoEntity));

                }
                long costMillis = endTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - beginTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
                if(costMillis>500){
                    logger.info("接口url：{}，接口参数：{}，接口执行耗时毫秒数：{}",operationUrl,argsStr,costMillis);
                }

            }


        }

    }

}
